/*
 * Copyright [2013-2021], Alibaba Group Holding Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.polardbx.matrix.jdbc;

import com.alibaba.polardbx.druid.sql.parser.ByteString;
import com.alibaba.polardbx.common.jdbc.ParameterContext;
import com.alibaba.polardbx.common.jdbc.ParameterMethod;
import com.alibaba.polardbx.common.jdbc.Parameters;
import com.alibaba.polardbx.executor.exception.ExecutorException;
import com.alibaba.polardbx.optimizer.context.ExecutionContext;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;

/**
 * @author mengshi.sunmengshi 2013-11-22 下午3:26:18
 * @since 5.0.0
 */
public class TPreparedStatement extends TStatement implements PreparedStatement {

    // 参数列表到参数上下文的映射 如 1:name 2：'2011-11-11'
    protected Parameters params = new Parameters();

    protected int autoGeneratedKeys = -1;
    protected int[] columnIndexes;
    protected String[] columnNames;

    public TPreparedStatement(TDataSource ds, TConnection conn, ByteString sql, ExecutionContext ec) {
        super(ds, conn, sql, ec);
    }

    @Override
    public boolean execute() throws SQLException {
        return super.executeInternal(sql, autoGeneratedKeys, columnIndexes, columnNames);
    }

    @Override
    public ResultSet executeQuery() throws SQLException {
        return this.executeQuery(sql);
    }

    @Override
    public int executeUpdate() throws SQLException {
        return executeUpdateInternal(sql, autoGeneratedKeys, columnIndexes, columnNames);
    }

    @Override
    protected void executeSQL(ByteString sql) throws SQLException {
        currentResultSet = conn.executeSQL(sql, params, this, executionContext);
    }

    @Override
    public void setShort(int parameterIndex, short x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setShort, new Object[] {parameterIndex, x}));

    }

    // 数据库从0开始，jdbc规范从1开始，jdbc到数据库要减一
    @Override
    public void setInt(int parameterIndex, int x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setInt, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setLong(int parameterIndex, long x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setLong, new Object[] {parameterIndex, x}));

    }

    @Override
    public void setBoolean(int parameterIndex, boolean x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setBoolean, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setString(int parameterIndex, String x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setString, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setFloat(int parameterIndex, float x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setFloat, new Object[] {parameterIndex, x}));

    }

    @Override
    public void setDouble(int parameterIndex, double x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setDouble, new Object[] {parameterIndex, x}));

    }

    @Override
    public void setBytes(int parameterIndex, byte[] x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setBytes, new Object[] {parameterIndex, x}));

    }

    @Override
    public void setDate(int parameterIndex, Date x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setDate1, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setDate2, new Object[] {parameterIndex, x, cal}));
    }

    @Override
    public void clearParameters() throws SQLException {
        this.params.getCurrentParameter().clear();
    }

    @Override
    public void setNull(int parameterIndex, int sqlType) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setNull1, new Object[] {parameterIndex, null}));
    }

    @Override
    public void setByte(int parameterIndex, byte x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setByte, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setBigDecimal, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setTime(int parameterIndex, Time x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setTime1, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setTimestamp1, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setAsciiStream, new Object[] {parameterIndex, x, length}));
    }

    @Override
    public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setUnicodeStream, new Object[] {parameterIndex, x, length}));
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setBinaryStream, new Object[] {parameterIndex, x, length}));
    }

    @Override
    public void setObject(int parameterIndex, Object x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setObject1, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setObject2, new Object[] {parameterIndex, x, targetSqlType}));
    }

    @Override
    public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setObject3, new Object[] {
                parameterIndex, x, targetSqlType,
                scaleOrLength}));
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setCharacterStream, new Object[] {parameterIndex, reader, length}));
    }

    @Override
    public void setRef(int parameterIndex, Ref x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setRef, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setBlob(int parameterIndex, Blob x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setBlob, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setClob(int parameterIndex, Clob x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setClob, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setArray(int parameterIndex, Array x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setArray, new Object[] {parameterIndex, x}));
    }

    @Override
    public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
        if (null == cal) {
            params.getCurrentParameter().put(parameterIndex,
                new ParameterContext(ParameterMethod.setTime1, new Object[] {parameterIndex, x}));
        } else {
            params.getCurrentParameter().put(parameterIndex,
                new ParameterContext(ParameterMethod.setTime2, new Object[] {parameterIndex, x, cal}));
        }
    }

    @Override
    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setTimestamp2, new Object[] {parameterIndex, x, cal}));

    }

    @Override
    public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setNull2, new Object[] {parameterIndex, null, typeName}));
    }

    @Override
    public void setURL(int parameterIndex, URL x) throws SQLException {
        params.getCurrentParameter().put(parameterIndex,
            new ParameterContext(ParameterMethod.setURL, new Object[] {parameterIndex, x}));
    }

    @Override
    public void addBatch() throws SQLException {
        params.addBatch();
    }

    @Override
    public void addBatch(String sql) throws SQLException {
        throw new UnsupportedOperationException(
            "addBatch(String sql) is not supported, please use addBatch() in PreparedStatement");
    }

    /**
     * 执行的顺序可能与传进来的顺序不同，因此返回值无意义
     */
    @Override
    public int[] executeBatch() throws SQLException {
        try {
            checkClosed();
            ensureResultSetIsEmpty();

            if (params.getBatchSize() == 0) {
                return new int[0];
            }
            executionContext.setAutoGeneratedKeys(autoGeneratedKeys);
            executionContext.setColumnIndexes(columnIndexes);
            executionContext.setColumnNames(columnNames);
            executeSQL(sql);
            int[] affectRows = new int[params.getBatchSize()];
            for (int i = 0; i < affectRows.length; i++) {
                if (currentResultSet.next()) {
                    if (currentResultSet instanceof TMultiResultSet) {
                        affectRows[i] = ((TMultiResultSet) currentResultSet).getAffectRows();
                    } else {
                        affectRows[i] = ((TResultSet) currentResultSet).getAffectRows();
                    }
                } else {
                    throw new ExecutorException("executeBatch error. expect affectRows :" + affectRows.length
                        + " but result size : " + (i + 1));
                }
            }

            return affectRows;
        } finally {
            clearBatch();
        }
    }

    @Override
    public void clearBatch() throws SQLException {
        checkClosed();
        params = new Parameters();
    }

    public void setParam(int parameterIndex, ParameterContext context) {
        params.getCurrentParameter().put(parameterIndex, context);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isAssignableFrom(this.getClass());
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        try {
            return (T) this;
        } catch (Exception e) {
            throw new SQLException(e);
        }
    }

    public int getAutoGeneratedKeys() {
        return autoGeneratedKeys;
    }

    public void setAutoGeneratedKeys(int autoGeneratedKeys) {
        this.autoGeneratedKeys = autoGeneratedKeys;
    }

    public int[] getColumnIndexes() {
        return columnIndexes;
    }

    public void setColumnIndexes(int[] columnIndexes) {
        this.columnIndexes = columnIndexes;
    }

    public String[] getColumnNames() {
        return columnNames;
    }

    public void setColumnNames(String[] columnNames) {
        this.columnNames = columnNames;
    }

    // ----------------- 不支持的方法 ------------------

    /**
     * 不支持
     */
    @Override
    public ResultSetMetaData getMetaData() throws SQLException {
        throw new UnsupportedOperationException("getMetaData");
    }

    @Override
    public void setPoolable(boolean poolable) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public boolean isPoolable() throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setRowId(int parameterIndex, RowId x) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setNString(int parameterIndex, String value) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setNClob(int parameterIndex, NClob value) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setClob(int parameterIndex, Reader reader) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public void setNClob(int parameterIndex, Reader reader) throws SQLException {
        throw new SQLException("not support exception");
    }

    @Override
    public ParameterMetaData getParameterMetaData() throws SQLException {
        throw new SQLException("getParameterMetaData");
    }

    @Override
    public Parameters getParams() {
        return params;
    }
}
